package view;

import java.awt.Color;
import java.awt.Point;

import javax.swing.JFrame;
import java.awt.GridLayout;


import javax.swing.ImageIcon;
import javax.swing.JButton;

import java.awt.BorderLayout;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.swing.JMenuBar;
import javax.swing.JPanel;

import model.Board;
import model.pieces.Bishop;
import model.pieces.King;
import model.pieces.Knight;
import model.pieces.Pawn;
import model.pieces.Queen;
import model.pieces.Rook;

import utils.IconNames;
import utils.PlayerSides;
import view.actionListeners.PlayerListener;

import controller.Controller;
//import controller.Controller;
import controller.GameManager;
import controller.Movement;

/**
 * Class manages the chess game GUI display
 */
public class ChessGUI {

	/**
	 * Class used to wrap parameters that are used to build the chess board presentation 
	 */
	private static class BoardParams {
		public Color light;
		public Color dark;
		public Color mark;
		public int size;
	}

	/**
	 * Holds information needed for clicked chess button
	 */
	public static class ClickedPiece {
		
		private Point position; // piece's position on board
		private List<Point> moves; // piece's move list
		
		public ClickedPiece(Point position) {
			
			// set members according to given values
			this.position = position;
			// initialize move list
			moves = new LinkedList<Point>();
		}

		public Point getPosition() { return position; }
		public void setPosition(int newRow, int newCol) { position.move(newRow, newCol); }
		public List<Point> getMoveList() { return moves; }
	}

	private static final int FRAME_SIZE = 750; // game display frame size	
	private static final int X_FRAME_BOUND = 100; // frame bounds 
	private static final int Y_FRAME_BOUND = 100;
	private static final int WIDTH_FRAME_BOUND = 450;
	private static final int HEIGHT_FRAME_BOUND = 300;		
	private static final int DELAY = 1000; // delay in display refresh	
	private static final String ICON_DIRECTORY = "src/view/icons/";
	
	private Controller gameManager; // manages game
	
	private Map<String, ImageIcon> iconMap; // hold board buttons' icons
	
	public AtomicBoolean humanMovementDone; // indicates if human player has chosen a move
	private AtomicBoolean isEnabled; // indicates if board buttons are enabled
	public Movement selectedMove; // holds human player's selected move
	
	private AddKingsPanel kingsPanel; // panel for adding Kings to the board
	private AddPiecePanel piecePanel; // panel for adding pieces (no Kings) to the board
	
	private BoardParams params; // board parameters
	private JFrame frame; // holds the board
	private List<JButton> boardButtons; // holds button for each position on chess board
	private ClickedPiece lastToBeClicked; // holds data of last-to-be-clicked button
	
	
	/**
	 * Constructor- creates an object to manage the chess GUI display
	 */
	public ChessGUI(){

		// allocate a Controller object
		gameManager = new GameManager(this);
		
		// allocate icon map
		iconMap = new HashMap<String, ImageIcon>();
		
		// set map's entries with each piece's icon
		setIcons();
		
		// initialize indicators
		isEnabled = new AtomicBoolean(false);		
		humanMovementDone = new AtomicBoolean(false);
		
		// no movement had been chosen yet
		selectedMove = null;
		
		// create the panels
		piecePanel = new AddPiecePanel(this, gameManager, gameManager.getMaxPiecesPertialGame());
		kingsPanel = new AddKingsPanel(this, gameManager, piecePanel);
		
		// set board parameters
		params = new BoardParams();
		params.light = Color.WHITE;
		params.dark = Color.DARK_GRAY;
		params.mark = Color.ORANGE;
		params.size = 8;
		
		lastToBeClicked = new ClickedPiece(new Point());
		
		// initialize board 
		initialize(params);
		
		// set board button listeners
		setListeners();
		
		// display game board
		displayBoard();		
	}

	/**
	 * Set icon map's entries with the board button icons
	 */
	private void setIcons() {
		
		iconMap.put(IconNames.WHITE+IconNames.PAWN, new ImageIcon(((new ImageIcon(ICON_DIRECTORY+"white-pawn.png")).getImage()).getScaledInstance(120, 120, java.awt.Image.SCALE_SMOOTH)));
		iconMap.put(IconNames.BLACK+IconNames.PAWN, new ImageIcon(((new ImageIcon(ICON_DIRECTORY+"black-pawn.png")).getImage()).getScaledInstance(70, 70, java.awt.Image.SCALE_SMOOTH)));
		
		// Knight icons
		iconMap.put(IconNames.WHITE+IconNames.KNIGHT, new ImageIcon(((new ImageIcon(ICON_DIRECTORY+"white-knight.png")).getImage()).getScaledInstance(80, 80, java.awt.Image.SCALE_SMOOTH)));
		iconMap.put(IconNames.BLACK+IconNames.KNIGHT, new ImageIcon(((new ImageIcon(ICON_DIRECTORY+"black-knight.png")).getImage()).getScaledInstance(90, 90, java.awt.Image.SCALE_SMOOTH)));
		
		// Rook icons
		iconMap.put(IconNames.WHITE+IconNames.ROOK, new ImageIcon(((new ImageIcon(ICON_DIRECTORY+"white-rook.png")).getImage()).getScaledInstance(90, 90, java.awt.Image.SCALE_SMOOTH)));
		iconMap.put(IconNames.BLACK+IconNames.ROOK, new ImageIcon(((new ImageIcon(ICON_DIRECTORY+"black-rook.png")).getImage()).getScaledInstance(90, 90, java.awt.Image.SCALE_SMOOTH)));
		
		// Bishop icons
		iconMap.put(IconNames.WHITE+IconNames.BISHOP, new ImageIcon(((new ImageIcon(ICON_DIRECTORY+"white-bishop.png")).getImage()).getScaledInstance(80, 80, java.awt.Image.SCALE_SMOOTH)));
		iconMap.put(IconNames.BLACK+IconNames.BISHOP, new ImageIcon(((new ImageIcon(ICON_DIRECTORY+"black-bishop.png")).getImage()).getScaledInstance(80, 80, java.awt.Image.SCALE_SMOOTH)));
		
		// King icons
		iconMap.put(IconNames.WHITE+IconNames.KING, new ImageIcon(((new ImageIcon(ICON_DIRECTORY+"white-king.png")).getImage()).getScaledInstance(90, 90, java.awt.Image.SCALE_SMOOTH)));
		iconMap.put(IconNames.BLACK+IconNames.KING, new ImageIcon(((new ImageIcon(ICON_DIRECTORY+"black-king.png")).getImage()).getScaledInstance(90, 90, java.awt.Image.SCALE_SMOOTH)));
		
		// Queen icons
		iconMap.put(IconNames.WHITE+IconNames.QUEEN, new ImageIcon(((new ImageIcon(ICON_DIRECTORY+"white-queen.png")).getImage()).getScaledInstance(70, 85, java.awt.Image.SCALE_SMOOTH)));
		iconMap.put(IconNames.BLACK+IconNames.QUEEN, new ImageIcon(((new ImageIcon(ICON_DIRECTORY+"black-queen.png")).getImage()).getScaledInstance(70, 85, java.awt.Image.SCALE_SMOOTH)));
		
		// Creature icons
		iconMap.put(IconNames.CREATURE, new ImageIcon(((new ImageIcon(ICON_DIRECTORY+"creature.png")).getImage()).getScaledInstance(90, 90, java.awt.Image.SCALE_SMOOTH)));
		iconMap.put(IconNames.CREATURE_COLLIDE, new ImageIcon(((new ImageIcon(ICON_DIRECTORY+"creature-collide.png")).getImage()).getScaledInstance(90, 90, java.awt.Image.SCALE_SMOOTH)));
		iconMap.put(IconNames.CREATURE_ATTACK, new ImageIcon(((new ImageIcon(ICON_DIRECTORY+"creature-attack.png")).getImage()).getScaledInstance(100, 100, java.awt.Image.SCALE_SMOOTH)));
	}
	
	/**
	 * Initialize the contents of the frame
	 */
	private void initialize(BoardParams params) {
		
		// create a new frame
		frame = new JFrame();//new JFrame();
		frame.setBounds(X_FRAME_BOUND, Y_FRAME_BOUND, WIDTH_FRAME_BOUND, HEIGHT_FRAME_BOUND);
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

		// set frame size
		frame.setSize(FRAME_SIZE, FRAME_SIZE);
		
		// disable window resize while running
		frame.setResizable(false);
		
		// set layout for the board buttons
		BorderLayout borderLayout = new BorderLayout();
		frame.getContentPane().setLayout(borderLayout);		
		JPanel boardPanel = new JPanel();
		frame.getContentPane().add(boardPanel, BorderLayout.CENTER);
		
		// build new game menu
		MenuManager menuManager = new MenuBuilder(); 
		JMenuBar menuBar = menuManager.buildNewGameMenu(gameManager);		

		// add it to the frame
		frame.setJMenuBar(menuBar);
		
		// create grid on which buttons will be placed
		GridLayout gridLayout = new GridLayout(params.size, params.size);
		boardPanel.setLayout(gridLayout);
		
		// allocate a list to hold all board buttons
		boardButtons = new ArrayList<JButton>(params.size*params.size);

		// add board buttons
		for (int i = 0; i < params.size * params.size; ++i) {
			JButton btn = new JButton("btn" + i);
			boardButtons.add(btn);

			// set each button's color according to its position on board
			if ((i/params.size) % 2 == 0) {
				btn.setBackground(params.dark);
				
				if (i % 2 == 0) {
					btn.setBackground(params.light);					
				} 
			} else {
				btn.setBackground(params.dark);
				
				if (i % 2 != 0) {
					btn.setBackground(params.light);					
				}
			}
			
			// set command-string
			String cmd = String.valueOf(i);
			btn.setActionCommand(cmd);			
			btn.setText("");			
			boardPanel.add(btn);			
		}		
	}

	/**
	 * Pairs each board button with an action listener
	 */
	private void setListeners() {

		// create listener
		PlayerListener listener = new PlayerListener(params.size, params.mark, this, gameManager, lastToBeClicked, boardButtons, isEnabled);
		
		// pair listener with all board buttons
		for (JButton currButton : boardButtons) {
			currButton.addActionListener(listener);
		}
	}

	/**
	 * Sets each button's color to default (according to its position).
	 * Uses game board to update locations of pieces, and updates buttons' icons accordingly
	 */
	public void displayBoard() {
		
		// total number of buttons of board 
		int buttonNumber = params.size * params.size;
		
		// scan all board buttons
		for (int i = 0 ; i < buttonNumber ; ++i) {
			
			// get current button from button list
			JButton currBtn = boardButtons.get(i);
									
			// calculate board entry matching current index
			int row = (i / params.size) + 1;
			int col = (i % params.size) + 1;
			
			// set color back to default according to button's position
			if ((i/params.size) % 2 == 0) {
				currBtn.setBackground(params.dark);
				
				if (i % 2 == 0) {
					currBtn.setBackground(params.light);					
				} 
			} else {
				currBtn.setBackground(params.dark);
				
				if (i % 2 != 0) {
					currBtn.setBackground(params.light);					
				}
			}
			
			// update current button's icon
			updateButtonIcon(gameManager.getBoard(), row, col, currBtn);
		}
	}

	/**
	 * Updates icon of given button, according to the piece at button's position on board
	 * @param gameBoard- reference to game board
	 * @param row- row index of current button
	 * @param col- column index of current button
	 * @param buttonToUpdate- button to update its icon
	 * @param iconMap- map of available icons
	 */
	private void updateButtonIcon(Board gameBoard, int row, int col, JButton buttonToUpdate) {
			
		// so far it is not known if a piece is at this position
		buttonToUpdate.setIcon(null);
		
		// check which piece (if any) is at this position
		if (gameBoard.getPiece(row, col) instanceof Pawn) { // check if a Pawn is placed
			
			// update image according to indicated player
			if (gameBoard.getPiece(row, col).getPlayer().equals(PlayerSides.Sides.White)) {
				// piece assigned to first player
				buttonToUpdate.setIcon(iconMap.get(IconNames.WHITE+IconNames.PAWN));
				
			} else {
				// piece assigned to second player
				buttonToUpdate.setIcon(iconMap.get(IconNames.BLACK+IconNames.PAWN));
			}
			
		} else if (gameBoard.getPiece(row, col) instanceof Knight) { // check if a Knight is placed
			
			if (gameBoard.getPiece(row, col).getPlayer().equals(PlayerSides.Sides.White)) {
				// piece assigned to first player
				buttonToUpdate.setIcon(iconMap.get(IconNames.WHITE+IconNames.KNIGHT));
				
			} else {
				// piece assigned to second player
				buttonToUpdate.setIcon(iconMap.get(IconNames.BLACK+IconNames.KNIGHT));
			}
			
		} else if (gameBoard.getPiece(row, col) instanceof Rook) { // check if a Rook is placed
			
			if (gameBoard.getPiece(row, col).getPlayer().equals(PlayerSides.Sides.White)) {
				// piece assigned to first player
				buttonToUpdate.setIcon(iconMap.get(IconNames.WHITE+IconNames.ROOK));
				
			} else {
				// piece assigned to second player
				buttonToUpdate.setIcon(iconMap.get(IconNames.BLACK+IconNames.ROOK));
			}
			
		} else if (gameBoard.getPiece(row, col) instanceof Bishop) { // check if a Bishop is placed
			
			if (gameBoard.getPiece(row, col).getPlayer().equals(PlayerSides.Sides.White)) {
				// piece assigned to first player
				buttonToUpdate.setIcon(iconMap.get(IconNames.WHITE+IconNames.BISHOP));
				
			} else {
				// piece assigned to second player
				buttonToUpdate.setIcon(iconMap.get(IconNames.BLACK+IconNames.BISHOP));
			}
			
		} else if (gameBoard.getPiece(row, col) instanceof King) { // check if a King is placed
			
			if (gameBoard.getPiece(row, col).getPlayer().equals(PlayerSides.Sides.White)) {
				// piece assigned to first player
				buttonToUpdate.setIcon(iconMap.get(IconNames.WHITE+IconNames.KING));
				
			} else {
				// piece assigned to second player
				buttonToUpdate.setIcon(iconMap.get(IconNames.BLACK+IconNames.KING));
			}
			
		} else if (gameBoard.getPiece(row, col) instanceof Queen) { // check if a Queen is placed
			
			if (gameBoard.getPiece(row, col).getPlayer().equals(PlayerSides.Sides.White)) {
				// piece assigned to first player
				buttonToUpdate.setIcon(iconMap.get(IconNames.WHITE+IconNames.QUEEN));
				
			} else {
				// piece assigned to second player
				buttonToUpdate.setIcon(iconMap.get(IconNames.BLACK+IconNames.QUEEN));
			}
		}
	}
	
	/**
	 * Updates icon of board button according to the piece at its position 
	 * @param pieceType- type of piece at button's position
	 * @param buttonPosition- board position of button to update
	 * @param pieceSide- piece's side
	 */
	public void updateButtonIcon(String pieceType, Point buttonPosition, PlayerSides.Sides pieceSide) {
	
		// get button to be updated
		int index = (buttonPosition.x-1)*params.size + buttonPosition.y-1; 
		JButton btnToUpdate = boardButtons.get(index);
		
		// update button icon
		if (pieceSide.equals(PlayerSides.Sides.White)) {
			
			// add a white piece
			btnToUpdate.setIcon(iconMap.get(IconNames.WHITE + pieceType));
			
		} else {
			
			// add a black piece
			btnToUpdate.setIcon(iconMap.get(IconNames.BLACK + pieceType));	
		}
				
		// update change on frame
		frame.validate();
	}
	
	/**
	 * Sets the enable state of the board buttons
	 * @param state- the state that the board button should be 
	 */
	public void setEnable(boolean state) {
		// set buttons' state as given value
		isEnabled.set(state);
	}
	
	/**
	 * Checks if human player is done choosing a movement
	 * @return True if human player is done choosing a movement, False if not
	 */
	public boolean isHumanMovementDone() { 
		
		// return indicator for choosing a move
		return humanMovementDone.get();
	}
	
	/**
	 * Adds panel to add pieces in order to allow user to add pieces to the board
	 */
	public void setBoardCustom() {
		
		// add panels to the frame
		frame.getContentPane().add(kingsPanel, BorderLayout.EAST);		
		frame.validate();
	}
	
	/**
	 * Removes given panel from the display frame
	 * @param panelToRemove- panel to remove from frame
	 */
	public void removePanel(JPanel panelToRemove) {
		
		// check if given panel is null
		if (panelToRemove == null) {
			
			// do nothing
			return;
		}
		
		// remove panel
		frame.remove(panelToRemove);
		
		// update frame
		frame.validate();
	}
	
	/**
	 * Adds given panel to the display frame
	 * @param newPanel- panel to add to frame
	 */
	public void addPanel(JPanel newPanel) {
		// add given panel
		frame.getContentPane().add(newPanel, BorderLayout.EAST);
	}
	
	/**
	 * Removes adding-pieces panel from the display frame
	 */
	public void removePiecePanel() {
		
		// remove panels from frame
		removePanel(piecePanel);
		removePanel(kingsPanel);
		
		// update frame
		frame.validate();
	}
	
	/**
	 * Gets positions and sets back to the default the color or the board buttons 
	 * at each given position 
	 * @param buttonsToUpdate- positions of buttons to update
	 */
	public void setDefaultColor(List<Point> buttonsToUpdate) {
		
		// scan given buttons 
		for (Point currPosition : buttonsToUpdate) {
			
			// get button matching current position on board
			int index = (currPosition.x-1)*params.size + currPosition.y-1; // convert row-column index to linear index
			JButton currBtn = boardButtons.get(index); 
	
			// set color back to default according to button's position
			if ((index/params.size) % 2 == 0) {
				currBtn.setBackground(params.dark);
				
				if (index % 2 == 0) {
					currBtn.setBackground(params.light);					
				} 
			} else {
				currBtn.setBackground(params.dark);
				
				if (index % 2 != 0) {
					currBtn.setBackground(params.light);					
				}
			}
		}
	}
	
	/**
	 * Initializes the icon for each button that held collided or killed Creatures
	 * @param collidedCreatures- positions of collided Creatures
	 * @param killedCreatures- positions of killed Creatures
	 */
	public void cleanKilledCreatures(List<Point> collidedCreatures, List<Point> killedCreatures) {
		
		// initialize icon of buttons at positions of killed Creatures and previous collisions
		List<Point> buttonsToClean = new LinkedList<Point>();
		buttonsToClean.addAll(killedCreatures);
		buttonsToClean.addAll(collidedCreatures);
		for (Point currPosition : buttonsToClean) {
			
			// get matching button index
			int buttonIndex = getButtonIndex(currPosition);
			
			// get button
			JButton currButton = boardButtons.get(buttonIndex);
			
			// set icon for the button
			currButton.setIcon(null);
		}
	}
	
	/**
	 * Refreshes the board display according to move made Creatures and Creature that killed a piece
	 */
	public void refreshDisplayAfterMove(Movement movement, Point killerCreature) {
		
		// current button index
		int buttonIndex;
		
		// current button
		JButton currButton;
		
		// update movement's source button's icon
		buttonIndex = getButtonIndex(movement.getSource());
		currButton = boardButtons.get(buttonIndex);
		currButton.setIcon(null);
		
		// update movement's destination button's icon
		buttonIndex = getButtonIndex(movement.getDestination());
		currButton = boardButtons.get(buttonIndex);
		updateButtonIcon(gameManager.getBoard(), movement.getDestination().x, movement.getDestination().y, currButton);
		
		// check if a Creature killed a piece on current turn
		if (killerCreature == null) {
			
			// no Creature killed a piece
			return;
		}
				
		// set icon of button at killer Creature's position
		buttonIndex = getButtonIndex(killerCreature);
		currButton = boardButtons.get(buttonIndex);
		currButton.setIcon(iconMap.get(IconNames.CREATURE_ATTACK));
	}
	
	/**
	 * Refreshes Creatures' positions and positions where two Creatures were collided 
	 */
	public void refreshDisplayBeforeMove(List<Point> creaturePositions, List<Point> collidingCreatures) {
		
		// set Creature icon for each button that holds a Creature
		for (Point currPosition : creaturePositions) {
			
			// get matching button index
			int index = getButtonIndex(currPosition);
			
			// get button
			JButton currButton = boardButtons.get(index);
			
			// set icon for the button
			currButton.setIcon(iconMap.get(IconNames.CREATURE));
		}
		
		// set collision icon for each position where two Creatures collided
		for (Point currPosition : collidingCreatures) {
			
			// get matching button index
			int index = getButtonIndex(currPosition);
			
			// get button
			JButton currButton = boardButtons.get(index);
			
			// set icon for the button
			currButton.setIcon(iconMap.get(IconNames.CREATURE_COLLIDE));
		}
		
		// delay display refresh
		if (!collidingCreatures.isEmpty()) {
			try {
				Thread.sleep(DELAY);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
	/**
	 * According to board button's position on board, calculates board button's index in board button list
	 * @param buttonPosition- button position on board
	 * @return button index in board button's list
	 */
	private int getButtonIndex(Point buttonPosition) {
		
		// convert row-column index to linear index
		return (buttonPosition.x-1)*params.size + buttonPosition.y-1; 
	}
	
	/**
	 * Launch the application
	 */
	public static void main(String[] args) {

		ChessGUI window = new ChessGUI();
		window.frame.setVisible(true);
	}
}